import stackless
import settings
import logging

import urllib2

from visss import webObject

from visss.objects import *

class Manager(object):
    '''
    ========================
    Main Manager (Stackless)
    ========================

    - Overview
        - features :
            - Lazy importing
            - Stackless channel controlled
            - multithread or exactly mutl-tasklet
            - settings do most of costums

        - Starting and Stop
            - Use the method *start_urls method to start the engine.
            - Send a Event to the manger.channel with *subject 'Shutdown'* to Stop
    '''
    

    def __init__(self,method=None,enabled=[],disabled=[]):

        '''
        __init__ 
        Take the *enabled* and *disabled* controller for arguements, also take **method**

        Method could be one of below :
            
            - 'enable'   *only* use the controllers in enabled
            - 'disable'  use the controllers *except* the ones in disabled
            - None       Usual usage and default value which means use everything
        '''

        self.channel=stackless.channel()
        self.balance_channel=stackless.channel()
        self.result_channel=stackless.channel()

        self.method=method
        self.enabled=enabled
        self.disabled=disabled

        self._handler=logging.StreamHandler('tmp.log')
        self._handler.setLevel(logging.DEBUG)

        self.log = logging.getLogger('manager')
        self.plog = logging.getLogger('manager.process')
        self.log.addHandler(self._handler)
        self.plog.addHandler(self._handler)

        self.loaded_controllers={}
        self.loaded_models={}
 
    def start_urls(self,urls):

        '''
        start_urls :
            take the *urls* to send in the self.channel
            this starts the manager
            *urls* should be **iterable**
        '''

        self.tasklet=stackless.tasklet(self.daemon)()
        self.tasklet.run()

        for url in urls :
            self.channel.send(Event('Parse Url',data=url))

        self.balance_tasklet=stackless.tasklet(self.balance_daemon)()
        self.balance_tasklet.run()


    def get_routings(self,get_mapper,prefix,data):
        '''
        get_routings :
            Settings controlled get_routings method
            It will get a mapper according to the *get_mapper* method
            which is usually *settings.get_mapper* or *settings.get_model*
            and returns a dict of arguments
            to get the mapper and send the arguement prefix to the get_mapper,
            See detals in settings.py
        '''
        m=get_mapper(self,prefix)

        try :
            kwargs=getattr(m,'match',getattr(m,'__getitem__',getattr(m,'__call__',None)))(data)

        except TypeError:
            raise IndexError,'A mapper must have a __call__ or match or __getitem__ method!'

        name = kwargs.pop('controller')
        action = kwargs.pop('action')
        try :
            args = kwargs.pop('args')
        except KeyError:
            args=()

        return Namespace(kwargs=kwargs,
                name=name,
                action=action,
                args=args,
                data=data
                )

    def daemon(self):

        '''
        Main Daemon:
            do *Nothing* except process *Shutdown* event
        '''

        while True :
            event=self.channel.receive()

            if event.subject == 'Shutdown' :

                for channel in self.loaded_controllers.values():
                    channel.send(Event('Shutdown'))

                for channel in self.loaded_models.values():
                    channel.send(Event('Shutdown'))

                break

            tasklet=stackless.tasklet(self.process)(event)
            tasklet.run()
            stackless.schedule()

        self.result_channel.send('Done')

    def process(self,event):

        '''
        Main method :
            this method take a event and process it
            the event subject should be one of below :

                -   *Store Item* the method will use get_routings to
                    get the model and action then send it to the model to store
                
                -   *Parse Url* the method will use get_routings to 
                    get the controller and action then send it to the controller to process

            Notice that all above is using stackless.channel to send a Event instead of calling them
            this causes the system fast :) , and all controller is lazy_import using manager.load

            See visss.core.py for details.

        '''

        if event.subject == 'Parse Url' :

            parsing=event.data

            url=webObject.get_url(parsing)

            try :
                data=self.get_routings(settings.get_mapper,settings.url_prefix(url),setiings.url_using(url))
                data.kwargs.update(url.get_data)

                try :
                    response=urllib2.urlopen(data.data)
                except :
                    response=urllib2.urlopen(url._url)

                response=webObject.Response(response)
                
                if self.method == 'disable' :
                    if data.name in self.disabled_list :
                        return
                
                elif self.method == 'enable' :
                    if not data.name in self.enabled_list :
                        return


                controller_channel = self.load(
                        data.name,settings.import_controller,self.loaded_controllers
                        )


                controller_channel.send(
                        Event('Parse Url',
                            action=data.action,
                            args=data.args,kwargs=data.kwargs,
                            response=response,
                            )
                    )
            except (IndexError,KeyError): # no mapper?
                pass

        elif event.subject == 'Store Item' :

            try :

                data=self.get_routings(
                       settings.get_model,settings.model_prefix(event.data),event.data
                        )

                model_channel = self.load(data.name,settings.import_model,self.loaded_models)

                model_channel.send(
                        Event('Store Item',
                            action=data.action,
                            args=data.args, kwargs=data.kwargs,
                            item=event.data,
                            )
                        )
            except (IndexError,KeyError):
                pass

            #Implent your own parse here

        else :

            raise NotImplementedError(event.subject)

    def balance_daemon(self):

        '''
        Balance Daemon
            This method controlls when to shut the mannager done.
            It will shut the manager when all the work is done :)
            It checks self.channel when its is balanced then check
            for every model and controller's balance

            See core.py for details
        '''

        while True :

            if self.channel.balance :
                balanced = True 

                channel_list = self.loaded_controllers.values() + self.loaded_models.values()

                if not channel_list :
                    balanced = False
                for channel in channel_list :

                    channel.send(Event('Check Balance'))
                    event=self.balance_channel.receive()

                    if not event.balanced :
                        balanced = False

                        break
                    
                if balanced :
                    self.channel.send(Event('Shutdown'))
                    break 

            stackless.schedule()

    def load(self,load_name,load_func,load_dict):

        '''
        lazy import
            the load_name should be the name to import 
            and load_func is usually defined in the settings.py
            load_dict is the dict where store loaded controller/models

            Notice that this method only store *channel*
        '''

            if not load_name in load_dict :
                load_channel=stackless.channel()
                load = load_func(load_name)(load_channel,self.channel,self.balance_channel)

                load_dict[load_name] = load_channel

            return load_dict[load_name]

